package co.gov.medellin.ssti.sub.paoyer.common.exception;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import javax.ejb.EJBException;

/**
 * Encargada de generar nuevamente las excepciones User o System que fueron
 * envueltas por el contenedor en una RemoteException.
 * 
 * @version 1.0
 */
public class ExceptionAssembler {

	private static final String DELIMITER_TOKEN = "_[[";
	private static final String JAVA_EXCEPTION_DELIMITER_TOKEN = "JE=[[";
	private static final String CUSTOM_MESSAGE_DELIMITER_TOKEN = "ME=[[";
	private static final String FINAL_DELIMITER_TOKEN = "]]";

	// TODO: ???
	private static final String SYSTEM_EXCEPTION_NAME = "com.cs.paoyer.common.exception.SystemException";
	private static final String USER_EXCEPTION_NAME = "com.cs.paoyer.common.exception.UserException";

	public static final int DO_NOTHING = -1;
	public static final int USER_EXCEPTION_CLASS = 0;
	public static final int SYSTEM_EXCEPTION_CLASS = 1;

	/**
	 * Devuelve la excepcion que fue lanzada anteriormente desde un modulo, y
	 * que se perdio dentro de la excepcion 'RemoteException' generada en un
	 * ambiente distribuido.
	 * 
	 * @param exceptionMessage
	 * @param exceptionClass
	 * @return
	 */
	public static EJBException getException(Exception exception) {
		// Objetos que crean la Instancia de la Clase dinamicamente
		Exception caugthJavaException = null;
		String customMessage = null;

		String exceptionMessage = exception.getMessage();
		int exceptionClass = 0;
		int codeMaxPos = 0;
		int delimiterTokenPos = exceptionMessage.indexOf(DELIMITER_TOKEN);

		String exceptionModule = null;
		String exceptionCode = null;
		String exceptionModuleName = null;
		String exceptionClassName = null;
		final ArrayList<String> modules = new ArrayList<String>();
		modules.add(0, "");
		modules.add(1,
				"co.gov.medellin.ssti.sub.paoyer.administration.exception.Administration");
		modules.add(2, "");
		modules.add(3, "");
		modules.add(4, "");
		modules.add(5, "");
		modules.add(6, "");
		modules.add(7, "");
		modules.add(8, "");
		modules.add(9,
				"co.gov.medellin.ssti.sub.paoyer.common.exception.Common");

		int numberExceptionCode = 0;

		// El mensaje de la excepci�n tiene el token ...
		if (delimiterTokenPos == -1) {
			return new EJBException(exception);
		}

		if (exceptionMessage.indexOf(SYSTEM_EXCEPTION_NAME) != -1) {
			// System Exception
			exceptionClass = SYSTEM_EXCEPTION_CLASS;
			codeMaxPos = 9;
		} else if (exceptionMessage.indexOf(USER_EXCEPTION_NAME) != -1) {
			// User Exception
			exceptionClass = USER_EXCEPTION_CLASS;
			codeMaxPos = 8;
		} else {
			return new EJBException(exception);
		}

		exceptionModule = exceptionMessage.substring(delimiterTokenPos + 3,
				delimiterTokenPos + 4);
		exceptionCode = exceptionMessage.substring(delimiterTokenPos + 5,
				delimiterTokenPos + codeMaxPos);
		// exceptionModuleName = PropertiesLoader.getInstance().getProperty(
		// "EXCEPTION_MODULE_" + exceptionModule);
		exceptionModuleName = modules.get(Integer.parseInt(exceptionModule))
				.toString();
		exceptionClassName = getExceptionClassName(exceptionModuleName,
				exceptionClass);

		try {
			numberExceptionCode = Integer.parseInt(exceptionCode);
		} catch (NumberFormatException e) {
			return new EJBException(exception);
		}

		switch (exceptionClass) {

		case USER_EXCEPTION_CLASS:
			return new UserException(new ModuleVO(exceptionModule,
					exceptionClassName), numberExceptionCode);

		case SYSTEM_EXCEPTION_CLASS:

			// Si existe, crea la excepci�n de Java generada originalmente.
			try {
				caugthJavaException = (Exception) createJavaException(exceptionMessage);
			} catch (ClassNotFoundException e) {
				return new EJBException(e);
			} catch (IllegalAccessException e) {
				return new EJBException(e);
			} catch (InstantiationException e) {
				return new EJBException(e);
			} catch (SecurityException e) {
				return new EJBException(e);
			} catch (NoSuchMethodException e) {
				return new EJBException(e);
			} catch (InvocationTargetException e) {
				return new EJBException(e);
			} catch (IllegalArgumentException e) {
				return new EJBException(e);
			}

			delimiterTokenPos = exceptionMessage
					.indexOf(CUSTOM_MESSAGE_DELIMITER_TOKEN);
			if (delimiterTokenPos != -1) {
				customMessage = exceptionMessage.substring(
						delimiterTokenPos + 5, exceptionMessage.indexOf(
								FINAL_DELIMITER_TOKEN, delimiterTokenPos));
			}

			return new SystemException(new ModuleVO(exceptionModule,
					exceptionClassName), numberExceptionCode,
					caugthJavaException, customMessage, false);

		default:
			return new EJBException(exception);
		}

	}

	/**
	 * Devuelve una cadena que representa el nombre de la instancia de la clase
	 * que envi� la excepci�n.
	 * 
	 * @param moduleName
	 * @param exceptionType
	 * @return
	 */
	private static String getExceptionClassName(String moduleName,
			int exceptionType) {
		String exceptionClassName = moduleName;

		switch (exceptionType) {
		case USER_EXCEPTION_CLASS:
			exceptionClassName = exceptionClassName + "UserException";
			break;
		case SYSTEM_EXCEPTION_CLASS:
			exceptionClassName = exceptionClassName + "SystemException";
			break;
		}

		return exceptionClassName;
	}

	/**
	 * Devuelve la excepcion Java originalmente lanzada por un modulo de la
	 * aplicacion.
	 * 
	 * @param exceptionMessage
	 *            Mensaje de la excepcion original.
	 * @return Object Un objeto Excepcion.
	 * 
	 * @throws java.lang.ClassNotFoundException
	 * @throws java.lang.IllegalAccessException
	 * @throws java.lang.InstantiationException
	 * @throws java.lang.SecurityException
	 * @throws java.lang.NoSuchMethodException
	 * @throws InvocationTargetException
	 * @throws java.lang.IllegalArgumentException
	 */
	private static Object createJavaException(String exceptionMessage)
			throws ClassNotFoundException, IllegalAccessException,
			InstantiationException, SecurityException, NoSuchMethodException,
			InvocationTargetException, IllegalArgumentException {

		Exception caugthJavaException = null;
		Class<?> exceptionDefinition = null;
		String javaExceptionClassName = null;
		String javaExceptionDescription = null;
		int delimiterTokenPos;

		// Valores de inicializacion.
		Class<?>[] strArgumentsClass = new Class[] { String.class };
		Object[] strArguments = null;
		Constructor<?> strExceptionConstructor;

		delimiterTokenPos = exceptionMessage
				.indexOf(JAVA_EXCEPTION_DELIMITER_TOKEN);
		if (delimiterTokenPos != -1) {
			javaExceptionClassName = exceptionMessage.substring(
					delimiterTokenPos + 5, exceptionMessage.indexOf(
							FINAL_DELIMITER_TOKEN, delimiterTokenPos));

			delimiterTokenPos = exceptionMessage.indexOf(DELIMITER_TOKEN,
					delimiterTokenPos + 5);

			javaExceptionDescription = exceptionMessage.substring(
					delimiterTokenPos + 3, exceptionMessage.indexOf(
							FINAL_DELIMITER_TOKEN, delimiterTokenPos));

			strArguments = new Object[] { javaExceptionDescription };

			// Crea un objeto a traves de uno de sus constructores. - Java
			// Reflection
			// 1. Create a Class object for the object you want to create.
			exceptionDefinition = Class.forName(javaExceptionClassName);

			// 2. Create a Constructor object by invoking getConstructor on the
			// Class object.
			strExceptionConstructor = exceptionDefinition
					.getConstructor(strArgumentsClass);

			// 3. Create the object by invoking newInstance on the Constructor
			// object.
			// Es equivalente a:
			// caugthJavaException = new Exception(strMessage);
			return caugthJavaException = (Exception) strExceptionConstructor
					.newInstance(strArguments);

		} else {
			return null;
		}
	}

	public static void main(String[] args) {
		String eMessage = "RemoteException EJB Exception: ; _[[0_103 ]] nested exception is: \n"
				+ "com.etb.saaw.common.exception.UserException:  Conexion JDBC "
				+ " del DataSource es NULL. \n"
				+ "Ocurrio un error obteniendo una conexion "
				+ " JDBC del DataSource.Verifique que los parametros del data source sean "
				+ "correctos y que la base de datos y el listener esten funcionando correctamente.";

		throw ExceptionAssembler.getException(new RemoteException(eMessage));

	}

}